package com.test.modules.token.service.impl;

import com.test.modules.token.service.Encoder;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Objects;

import java.util.Random;

/**
 * long数据类型编解码器。
 * 编码的结果尽可能散列。
 *
 * 结果字符串由2部分组成：编码串（n位，根据long数值的大小决定）+ 字符集数组起点位置标示（1位）
 *
 * @author qd-hz
 */
@Slf4j
public class LongEncoder implements Encoder<Long> {
    private static final char[] CHARSET = new char[]{
            'L', 'y', 'a', 'n', 'q', 'Q', 'h', '4', 'R', 'M',
            '1', 'W', 'c', 'k', '3', '0', 'D', 'S', '9', 'H',
            'r', 'd', '5', 'g', 'C', 'U', 'p', 'G', 'i', 'P',
            's', 'o', 't', '8', 'b', 'Y', 'N', 'e', '6', 'w',
            'u', 'K', 'x', 'Z', 'A', '2', 'J', 'E', 'v', 'f',
            'j', 'F', '7', 'X', 'B', 'l', 'z', 'm', 'T', 'V'
    };
    /**
     * 余数模
     */
    private static final int REMAINDER_MODE = 4;

    private static final long MIX_UP_FACTOR = 65535;

    public static void main(String[] args) {
        LongEncoder encoder = new LongEncoder();
        for (int i = 0; i < 100; i++) {
            long timestamp = System.currentTimeMillis();
            String code = encoder.encode(timestamp);
            Long decode = encoder.decode(code);
            log.info("source={}, code={}, decode={}, equals={}", timestamp, code, decode, Objects.areEqual(timestamp, decode));
        }
    }

    public String encode(Long source) {
        int mode = CHARSET.length;
        char[] buf = new char[32];
        int charPos = 32;

        int remainder = (int) (source % REMAINDER_MODE);
        int charsetBaseIndex = getCharsetBaseIndex(remainder);
        char charsetBaseChar = CHARSET[charsetBaseIndex];
        buf[--charPos] = charsetBaseChar;

        source = source ^ MIX_UP_FACTOR;

        while ((source / mode) > 0) {
            int ind = getIndexOfLastPosition(source, mode, charsetBaseIndex);
            buf[--charPos] = CHARSET[ind];
            source /= mode;
        }
        // 最左侧一位的位置
        int lastIndex = getIndexOfLastPosition(source, mode, charsetBaseIndex);
        buf[--charPos] = CHARSET[lastIndex];
        String str = new String(buf, charPos, (32 - charPos));
        return str;
    }

    public Long decode(String code) {
        int mode = CHARSET.length;
        char chs[] = code.toCharArray();
        long res = 0L;

        char charsetBaseChar = chs[chs.length - 1];
        int charsetBaseIndex = indexOfChar(mode, charsetBaseChar, CHARSET);

        for (int i = 0; i < chs.length - 1; i++) {
            int ind = indexOfChar(mode, chs[i], CHARSET);
            if (ind == -1) {
                return null;
            }

            int realValue = getValueFromIndex(ind, mode, charsetBaseIndex);
            if (i > 0) {
                res = res * mode + realValue;
            } else {
                res = realValue;
            }
        }

        res = res ^ MIX_UP_FACTOR;

        int remainder = (int) (res % REMAINDER_MODE);
        int remainderCheck = charsetBaseIndex % REMAINDER_MODE;
        if (remainder != remainderCheck) {
            return null;
        }
        return res;
    }


    /**
     * 从字符集的逻辑起点开始，当前位（最右面一位）所在的位置
     *
     * @param source
     * @param mode
     * @param charsetBaseIndex
     * @return
     */
    private int getIndexOfLastPosition(long source, int mode, int charsetBaseIndex) {
        int ind = (int) ((source + charsetBaseIndex) % mode);
        return ind;
    }

    /**
     * 根据字符所在索引的值，计算字符表示的值
     *
     * @param index
     * @param mode
     * @param charsetBaseIndex
     * @return
     */
    private int getValueFromIndex(int index, int mode, int charsetBaseIndex) {
        int value = (index + mode - charsetBaseIndex) % mode;
        return value;
    }

    /**
     * 获取字符集的起点位置
     */
    private int getCharsetBaseIndex(int remainder) {
        int maxMixFactor = CHARSET.length / REMAINDER_MODE;
        Random random = new Random();
        int randomMixFactor = random.nextInt(maxMixFactor);
        int baseIndex = randomMixFactor * REMAINDER_MODE + remainder;

        return baseIndex;
    }

    private int indexOfChar(int mode, char ch, char[] currentCharset) {
        int ind = -1;
        for (int j = 0; j < mode; j++) {
            if (ch == currentCharset[j]) {
                ind = j;
                break;
            }
        }
        return ind;
    }
}
